package Stream;

import java.util.ArrayList;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * Stream类的reduce函数介绍
 * 参考链接：https://blog.csdn.net/icarusliu/article/details/79504602
 * reduce函数有三种重载形式
 */
public class Reduce {

    /**
     * 一个参数的reduce()
     * Optional<T> Reduce(BinaryOperator<T> accumulator)
     * 示例：输出求和与求最大值的结果是否与预期相同
     */
    public void one_para() {
        Stream<Integer> s = Stream.of(1, 2, 3, 4, 5, 6);
        /*
        求和，也可以写成lambda语法：Integer sum = s.Reduce((a, b) -> a + b).get();
         */
        Integer sum = s.reduce(new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer, Integer integer2) {
                return integer + integer2;
            }
        }).get();

        /*
        求最大值，也可以写成lambda语法：Integer max = s.Reduce((a, b) -> a >= b ? a : b).get()
        Stream对象不能反复使用，加入build代码，报ArrayList不能转为Integer，看来这个builder是一个一个add的，算了
        还是再来一遍赋值，好奇有没有什么办法可以聪明点。
         */
        /*
        Stream.Builder sb = Stream.builder();
        Integer[] array = new Integer[]{1, 2, 3, 4, 5, 6};
        List<Integer> o = Arrays.asList(array);
        sb.add(o);
        s = sb.build();
        */
        s = Stream.of(1, 2, 3, 4, 5, 6);
        Integer max = s.reduce(new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer, Integer integer2) {
                return integer >= integer2 ? integer : integer2;
            }
        }).get();
        System.out.println("sum is 21: " + (sum == 21));
        System.out.println("max is 6: " + (max == 6));
    }

    /**
     * 两个参数的reduce()
     * T Reduce(T identity, BinaryOperator<T> accumulator)
     * 示例：输出所有元素连到一起并在最前面添加[value]
     */
    public void two_para() {
        Stream<String> s = Stream.of("test", "t1", "t2", "te", "aaa", "ta");
        /*
        结果将是：[value]testt1t2teaaata，也可使用lambda语法：System.out.println(s.Reduce("[value]", (s1, s2) -> s1.concat(s2)))
         */
        System.out.println(s.reduce("[value]", new BinaryOperator<String>() {
            @Override
            public String apply(String s, String s2) {
                return s.concat(s2);
            }
        }));
    }

    /**
     * 三个参数的reduce()，串行流时第三个参数无效
     * <U> U Reduce(U identity, BiFunction<U, ? supper T, U> accumulator, BinaryOperator<U> combiner)
     * 示例：将一个流中的元素从String变为List<String>，如“ab”变为“[ab]”
     */
    public void three_para() {
        Stream<String> s = Stream.of("ab", "cd");
        /*
        结果将是[["ab"],["cd"]]，
        也可使用lambda语法：System.out.println(s.Reduce(new ArrayList<String>,
                                                (s1, s2) -> new ArrayList<>(s1, s2), (r1, r2) -> r1.addAll(r2))
         */
        System.out.println(s.reduce(new ArrayList<ArrayList<String>>(), new BiFunction<ArrayList<ArrayList<String>>, String, ArrayList<ArrayList<String>>>(){
            @Override
            public ArrayList<ArrayList<String>> apply(ArrayList<ArrayList<String>> r, String s) {
                ArrayList<String> h = new ArrayList<String>();
                h.add(s);
                r.add(h);
                return r;
            }
        }, new BinaryOperator<ArrayList<ArrayList<String>>>() {
            @Override
            public ArrayList<ArrayList<String>> apply(ArrayList<ArrayList<String>> r, ArrayList<ArrayList<String>> r2) {
                r.addAll(r2);
                return r;
            }
        }));
    }

    /**
     * 三个参数的reduce()，并行流使得第三个参数生效
     * Stream.parallel()进行流化
     * 示例：预期求和结果为10，实际为18，并行似乎有几个并行的就每个并行都会加上identity，最后combine发现identity多次使用导致和串行的结果不同
     */
    public void three_para_parallel() {
        /**
         * lambda语法：
         * System.out.println(Stream.of(1,2,3).parallel().
         *                  reduce(4, (s1, s2) -> s1 + s2, (s1, s2) -> s1 + s2))
         */
        System.out.println(Stream.of(1,2,3).parallel().reduce(4, new BiFunction<Integer, Integer, Integer>() {
            @Override
            public Integer apply(Integer integer, Integer integer2) {
                return integer + integer2;
            }
        }, new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer, Integer integer2) {
                return integer + integer2;
            }
        }));
    }

    /**
     * 三个参数的reduce()
     * 不会出现上面的求和翻倍情况，因为第一个参数为引用类型，每个并行分段用的初始identity是同个引用，最好第一个参数使用线程安全类型，
     * 如Collections.synchronizedList(new ArrayList<String>())
     */
    public void three_para_ext() {
        /**
         * 模拟Filter查找其中含有字母a的所有元素，打印结果将是aa ab ad
         * lambda语法：
         * s1.parallel().reduce(new ArrayList<String>(), (r, t) -> {if (predicate.test(t)) r.add(t); return r;},
         * (r1,r2) -> {System.out.println(r1==r2); return r2;}).stream.forEach(System.out::println);
         */
        Stream<String> s1 = Stream.of("aa", "ab", "c", "ad");
        Predicate<String> predicate = t -> t.contains("a");
        s1.parallel().reduce(new ArrayList<String>(), new BiFunction<ArrayList<String>, String, ArrayList<String>>() {
            @Override
            public ArrayList<String> apply(ArrayList<String> strings, String s) {
                if (predicate.test(s)) {
                    strings.add(s);
                }
                return strings;
            }
        }, new BinaryOperator<ArrayList<String>>() {
            @Override
            public ArrayList<String> apply(ArrayList<String> strings, ArrayList<String> strings2) {
                System.out.println(strings == strings2);
                return strings;
            }
        }).stream().forEach(System.out::println);

        //下面这块不知道为啥转出不了ad，找到原因了,不能复用前面的predicate，这个变量在前面用过，再在这里使用就会将ad打印成null
        //给predicate再赋一遍值也不行，因为reduce里的lambda表达式会报predicate需要是final的，所以这里再定义一个predicate1变量
        //不过运行main会发现每次下面的输出都不一样，以后再探究
        s1 = Stream.of("aa", "ab", "c", "ad");
        Predicate<String> predicate1 = t -> t.contains("a");
        s1.parallel().reduce(new ArrayList<String>(), (r, t) -> {if(predicate1.test(t)) r.add(t); return r;},
                (r1, r2) -> {System.out.println(r1 == r2); r1.addAll(r2); return r1;}).stream().forEach(System.out::println);
    }
    public static void main(String... args) {
        Reduce reduce = new Reduce();
        reduce.one_para();
        reduce.two_para();
        reduce.three_para();
        reduce.three_para_parallel();
        reduce.three_para_ext();
    }
}
